package app.server.project.core.websocket;

import app.server.project.core.constants.Def;
import app.server.project.core.constants.TcpCmd;
import app.server.project.core.exception.SysError;
import app.server.project.core.token.TokenCache;
import app.server.project.core.websocket.model.LoginReq;
import app.server.project.core.websocket.model.Online;
import app.server.project.core.websocket.model.PushUser;
import app.server.project.core.websocket.model.WSOffline;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Component
@ServerEndpoint("/ws")
@Slf4j
public class WSMgr {

    private final static int TIME_OUT = 5 * 1000;


    private static final ConcurrentHashMap<Long, PushUser> idMap = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<Session, PushUser> sessionMap = new ConcurrentHashMap<>();
    private static ApplicationEventPublisher applicationEventPublisher;

    public static void init(ApplicationEventPublisher publisher) {
        applicationEventPublisher = publisher;
    }

    public static int getOnlineNum() {
        return idMap.size();
    }

    private static void chkLogin(Session session, WSDataModel dataModel) {
        try {
            log.info("登录参数:{}", dataModel);
            var loginReq = LoginReq.of();
            BeanUtil.fillBeanWithMapIgnoreCase((Map<?, ?>) dataModel.getData(), loginReq, true);
            if (StrUtil.isBlank(loginReq.getToken())) {
                session.getBasicRemote().sendText(JSONUtil.toJsonStr(WSDataModel.of(dataModel.getCode(), SysError.Token)));
                log.info("登录错误,token为空");
                session.close();
                return;
            }
            if (!TokenCache.chkToken(loginReq.getUserId(), loginReq.getToken())) {
                session.getBasicRemote().sendText(JSONUtil.toJsonStr(WSDataModel.of(dataModel.getCode(), SysError.ErrorToken)));
                log.info("登录错误,缓存中的token不相同");
                session.close();
                return;
            }
            session.getBasicRemote().sendText(JSONUtil.toJsonStr(WSDataModel.of(dataModel.getCode(), Def.Zero)));
            var pushUser = PushUser.of(loginReq.getUserId(), session);
            idMap.put(loginReq.getUserId(), pushUser);
            sessionMap.put(session, pushUser);
            applicationEventPublisher.publishEvent(Online.of(pushUser.getUserId()));
            log.info("userid:{}:websocket 连接成功,发送登录事件", loginReq.getUserId());
        } catch (Exception e) {

        }

    }

    private static void doHeart(Session session, WSDataModel dataModel) {
        PushUser pushUser = sessionMap.get(session);
        if (Objects.isNull(pushUser)) return;
        try {
            session.getBasicRemote().sendText(JSONUtil.toJsonStr(dataModel));
        } catch (Exception e) {

        }
    }

    public static void push(long id, int cmd, Object data) {
        var pushUser = idMap.get(id);
        if (Objects.isNull(pushUser)) return;
        pushUser.push(cmd, data);
    }

    public static void broadcast(int cmd, Object data) {
        idMap.forEach((k, v) -> {
            push(k, cmd, data);
        });
    }

    public static void pushError(long id, int cmd, int errorCode) {
        push(id, cmd, errorCode);
    }

    public static void kick(long userId) {
        PushUser pushUser = idMap.get(userId);
        if (Objects.isNull(pushUser)) return;
        //踢人
        pushUser.push(TcpCmd.Kick, null);
        idMap.remove(pushUser.getUserId());
        Session session = null;
        for (var entry : sessionMap.entrySet()) {
            if (entry.getValue().getUserId() == userId) {
                session = entry.getKey();
                break;
            }
        }
        if (Objects.nonNull(session)) {
            sessionMap.remove(session);
            try {
                session.close();
            } catch (Exception e) {
            }
        }

        applicationEventPublisher.publishEvent(WSOffline.of(pushUser.getUserId()));
        log.info("userid:{}:触发离线事件", pushUser.getUserId());
    }

    @OnOpen
    public void onOpen(Session session) {
        session.setMaxIdleTimeout(TIME_OUT);
    }

    @OnClose
    public void onClose(Session session) {
        PushUser pushUser = this.sessionMap.get(session);
        if (Objects.isNull(pushUser)) return;
        idMap.remove(pushUser.getUserId());
        sessionMap.remove(session);
        applicationEventPublisher.publishEvent(WSOffline.of(pushUser.getUserId()));
        log.info("userid:{}:触发离线事件", pushUser.getUserId());
    }

    @OnMessage
    public void onMsg(Session session, String msg) {
        if (Objects.isNull(msg)) return;
        var data = JSONUtil.toBean(msg, WSDataModel.class);
        if (Objects.isNull(data)) return;
        switch (data.getCode()) {
            case TcpCmd.Login: {
                chkLogin(session, data);
            }
            break;
            case TcpCmd.Heart: {
                doHeart(session, data);
            }
            break;
        }
    }

}
